﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace SocketServer
{
    class Server
    {
        public Server(Logger logger)
        {
            this.logger = logger;
        }

        public void Start(int port)
        {
            this.port = port;
            Thread serve = new Thread(this.Serve);
            serve.IsBackground = true;
            serve.Start();
        }

        public void Stop()
        {
            Cleanup();
        }

        private void Cleanup()
        {
            if (this.client != null)
            {
                this.client.Close();
                this.client = null;
                this.port = 0;
            }
            if (this.server != null)
            {
                this.server.Stop();
                this.server = null;
            }
        }

        private void Serve()
        {
            this.logger.ServerLog(String.Format("Starting server at port: {0}\n", this.port));
            server = null;

            try
            {
                IPAddress localAddr = IPAddress.Parse("127.0.0.1");
                server = new TcpListener(localAddr, this.port);

                server.Start();

                // Enter the listening loop.

                while (true)
                {
                    this.logger.ServerLog("Waiting for a connection... \n");
                    try
                    {
                        this.client = server.AcceptTcpClient();
                    }
                    catch
                    {
                        Cleanup();
                        return;
                    }
                    this.logger.ServerLog("Connected!\n");  

                    // Get a stream object for reading and writing
                    NetworkStream stream = this.client.GetStream();
                    int i;
                    // Buffer for reading data
                    Byte[] bytes = new Byte[256];

                    // Loop to receive all the data sent by the client.
                    try
                    {
                        while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                        {
                            ProcessData(stream, bytes, i);
                            
                        }
                    }
                    catch (Exception e)
                    {
                        this.logger.ServerLog(String.Format("Connection Closed.\n"));
                        Console.Write(String.Format("Exception: {0}\n", e));
                        bytes = null;
                        throw e;
                    }

                }
            }
            catch (Exception e)
            {
                Console.Write(String.Format("Exception: {0}\n", e));
            }
            finally
            {
                Cleanup();
            }

        }

        protected virtual void ProcessData(NetworkStream stream, Byte[] buffer, int i)
        {
            String data = null;

            // Translate data bytes to a ASCII string.
            data = System.Text.Encoding.ASCII.GetString(buffer, 0, i);
            this.logger.Dump(String.Format("Received: {0}\n", data));
        }

        protected TcpClient client;
        protected TcpListener server;
        protected Logger logger;
        protected int port;
    }

    class LoopbackServer : Server
    {
        public LoopbackServer(Logger logger) : base(logger) {}

        protected sealed override void ProcessData(NetworkStream stream, Byte[] buffer, int i)
        {
            String data = null;

            // Translate data bytes to a ASCII string.
            data = System.Text.Encoding.ASCII.GetString(buffer, 0, i);
            base.logger.Dump(String.Format("Received: {0}\n", data));

            // Process the data sent by the client.
            data = data.ToUpper();

            byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);

            // Send back a response.
            stream.Write(msg, 0, msg.Length);
            this.logger.Dump(String.Format("Sent: {0}\n", data));
        }
    }

}
